package kernel.stl.slicing.regiondetection;

import kernel.stl.LineSegment;
import kernel.stl.LineSegmentList;
import kernel.stl.PointXY;
import kernel.stl.exception.InvalidConnectionException;
import kernel.stl.slicing.Slice;

/**O RegionDetector é responsável por fazer a análise de uma fatia e separar as
 * regiões existentes nela. Uma região nada mais é do que um conjunto de
 * segmentos de reta que são ligados entre si.
 * @author George
 */
public class RegionDetector {

    private Slice slice;

    /**Construtor do detector de regiões.
     * @param slice fatia a ser analisada para gerar as regiões.
     */
    public RegionDetector(Slice slice) {
        this.slice = slice;
    }
    
    public void connectAdjacentRegions(RegionList list) {
        for (int i = 0; i < list.getRegionCount(); i++) {
            for (int j = 0; j < list.getRegionCount(); j++) {
                if (i != j) {
                    Region region1 = list.getRegion(i);
                    Region region2 = list.getRegion(j);
                    if (region1.isAdjacent(region2))
                        region1.connect(region2, j);
                }
            }
        }        
    }
    
    public void joinRegions(RegionList list, double tolerance) { 
        int i = 0;
        int j = 0;
        
        while (i < list.getRegionCount()) {
            j = 0;
            while (j < list.getRegionCount()) {
                if (i != j) {
                    Region region1 = list.getRegion(i);
                    Region region2 = list.getRegion(j);
                    if (region1.canJoin(region2, tolerance)) {
                        region1.join(region2, tolerance);
                        
                        list.remove(j);
                        if (j < i) 
                            i--;
                        j--;
                    }
                }
                j++;
            }
            i++;
        }   
    }
    
    public boolean canAddLineIntRegion(LineSegment line, Region region, double tolerance) {
        PointXY point1 = line.getPoint1().toPointXY();
        PointXY point2 = line.getPoint2().toPointXY();
        
        if (region.getPointCount() == 0)
            return true;
        else if (region.getLastPoint().equals(point1, tolerance))
            return true;
        else if (region.getLastPoint().equals(point2, tolerance))
            return true;
        else if (region.getFirstPoint().equals(point1, tolerance))
            return true;
        else if (region.getLastPoint().equals(point2, tolerance))
            return true;
        else
            return false;
    }
    
    public void addLineIntoRegion(LineSegment line, Region region, double tolerance) {
        PointXY point1 = line.getPoint1().toPointXY();
        PointXY point2 = line.getPoint2().toPointXY();
        
        if (region.getPointCount() == 0) {
            region.addPoint(point1);
            region.addPoint(point2);
        }        
        else {
            if (region.getLastPoint().equals(point1, tolerance))
                region.addPoint(point2);
            else if (region.getLastPoint().equals(point2, tolerance))
                region.addPoint(point1);
            else if (region.getFirstPoint().equals(point1, tolerance))
                region.addPointInFristPosition(point2);
            else if (region.getLastPoint().equals(point2, tolerance))
                region.addPointInFristPosition(point1);
            //else lançar exceção                        
        }        
    }
    
    private RegionList removeRedundantPointsFromRegions(RegionList list) {
        RegionList resultRegionList = new RegionList();
        RegionList regionList;
        
        for (int i = 0; i < list.getRegionCount(); i++) {
            regionList = list.getRegion(i).removeUnnecessaryPointsForCycle();
            if (regionList.getRegionCount() > 0)
                resultRegionList.add(regionList);
        }
        
        return resultRegionList;        
    }
       
    /**Detecta a lista de regiões contidas em uma fatia.
     * A fatia utilizada na detecção das regiões é definida no construtor desta
     * classe. Além disto, a detecção leva em consideração uma tolerância que 
     * corresponde à máxima diferença entre as componentes x, y e z de pontos de
     * dois segmentos de reta para determinar se eles estão ligados ou não.
     * @param tolerance distância máxima entre dois pontos para considerá-los iguais.
     * @return Lista das regiões detectadas.
     * @throws kernel.stl.exception.InvalidConnectionException ocorre quando o 
     * programa tenta fazer uma conexão inválida entre duas regiões ou entre
     * uma região e uma reta.
     */
    public RegionList detect(double tolerance) throws InvalidConnectionException {
        
        this.slice = slice.newResizedLayer(500, 500);
        
        RegionList listOfRegions = new RegionList();
        LineSegmentList list = this.slice.getLineSegmentList();
        boolean lineWasAdded;
                
        for (int i = 0; i < list.getLineSegmentCount(); i++) {
            LineSegment line = list.getLineSegment(i);
            lineWasAdded = false;
                
            for (int j = 0; j < listOfRegions.getRegionCount(); j++) {                 
                Region region = listOfRegions.getRegion(j);
                if (this.canAddLineIntRegion(line, region, tolerance)) {
                    this.addLineIntoRegion(line, region, tolerance);
                    lineWasAdded = true;
                }              
            } 
            if (!lineWasAdded) {
                Region region = new Region();
                this.addLineIntoRegion(line, region, tolerance);
                listOfRegions.addRegion(region);
            }
            
            this.joinRegions(listOfRegions, tolerance);
        }
        
        this.connectAdjacentRegions(listOfRegions);
        this.joinRegions(listOfRegions, tolerance);
        RegionList regionList = this.removeRedundantPointsFromRegions(listOfRegions);
        
        listOfRegions.add(regionList);
        
        return listOfRegions;
    }
}
